﻿#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>


/*
* 可以切换X86环境和X64环境分别运行对比效果
* X86环境是32位机器
* X64环境是64位机器
* sizeof的结果是以字节为单位的，所以
* X86环境下指针变量大小为4字节
* X64环境下指针变量大小为8字节
*/
//int main()
//{
//	printf("sizeof(char*)=%zd\t\t", sizeof(char*));
//	printf("sizeof(int*)=%zd\t\t", sizeof(int*));
//	printf("sizeof(float*)=%zd\t\t", sizeof(float*));
//	printf("sizeof(double*)=%zd\t\n", sizeof(double*));
//
//	printf("sizeof(char)=%zd\t\t", sizeof(char));
//	printf("sizeof(int)=%zd\t\t", sizeof(int));
//	printf("sizeof(float)=%zd\t\t", sizeof(float));
//	printf("sizeof(double)=%zd\t", sizeof(double));
//	return 0;
//}


//指针的类型决定了，对指针解引⽤的时候有多⼤的权限（⼀次能操作⼏个字节）
//⽐如： char* 的指针解引⽤就只能访问⼀个字节，⽽ int* 的指针的解引⽤就能访问四个字节
//int main()
//{
//	int n = 0x11223344;
//	int* p1 = &n;
//	char* p2 = (char*)&n;
//	double* p3= (double*)&n;
//	*p1 = 0;
//	//p1指针类型与指针指向的数据是同一种数据类型
//	//所以对p1进行解引用并且赋值为0，可以使n=0；
//	n = 0x11223344;
//	*p2 = 0;
//	//p2指针类型与指针指向的数据不是同一种数据类型//p2指向的数据类型较小
//	//所以对p2进行解引用并且赋值为0，仅可将n=0x11223300;
//	n = 0x11223344;
//	*p3 = 0;
//	//p3指针类型与指针指向的数据不是同一种数据类型//p3指向的数据类型较大
////所以对p2进行解引用并且赋值为0，
//// 不仅会将n=0;并且会将内存中与n相邻的四字节（当前环境中double大小为8字节）数据全部清零
//	//会引发严重的后果
//	return 0;
//}


//指针的类型决定了指针向前或者向后⾛⼀步有多⼤（距离）。
//int main()
//{
//	int n = 0x11223344;
//	int* p1 = &n;
//	char* p2 = (char*)&n;
//	printf("&n\t=\t%p\n", &n);
//	printf("p1\t=\t%p\n", p1);
//	printf("p1+1\t=\t%p\n", p1 + 1);
//	printf("p2\t=\t%p\n", p2);
//	printf("p2+1\t=\t%p\n", p2 + 1);
//	printf("p2-1\t=\t%p\n", p2 - 1);
//	return 0;
//}


//在指针类型中有⼀种特殊的类型是 void* 类型的，可以理解为⽆具体类型的指针（或者叫泛型指
//针），这种类型的指针可以⽤来接受任意类型地址。但是也有局限性， void* 类型的指针不能直接进
//⾏指针的 + -整数和解引⽤的运算。
//int main()
//{
//	int a = 10;
//	void* pa = &a;
//	void* pc = &a;
//	//*pa = 10;
//	//*pc = 0;
//	return 0;
//}
//⼀般 void* 类型的指针是使⽤在函数参数的部分，⽤来接收不同类型数据的地址，这样的设计可以
//实现泛型编程的效果。使得⼀个函数来处理多种类型的数据，在《深⼊理解指针(4)》中我们会讲解。


//const 修饰的变量仍然可以使用指针去改变
//int main()
//{
//	const int a = 10;
//	int* pa = &a;
//	a = 100;//不能修改const修饰的变量
//	printf("a=%d\n", a);
//	*pa = 100;//但是使用指针然后解引用就可以绕过这层限制//实际应用不要这样做
//	printf("a=%d\n", a);
//	return 0;
//
//}


//如何限制指针修改其指向的数据？
//const如果放在* 的左边，修饰的是指针指向的内容，保证指针指向的内容不能通过指针来改变。
//但是指针变量本⾝的内容可变
//const如果放在* 的右边，修饰的是指针变量本⾝，保证了指针变量的内容不能修改，但是指针指
//向的内容，可以通过指针改变。



////指针变量的 *左右两边均不加const
////可以改变指针变量指向的数据的值，也可以改变指针变量的值
//void text1(void)
//{
//	int a = 10;
//	int b = 10;
//	int* p = &a;
//	*p = 10;
//	p = &b;
//}
//
////指针变量的 *左边加const
////不可以改变指针变量指向的数据的值，可以改变指针变量的值
//void text2(void)
//{
//	int a = 10;
//	int b = 10;
//	int const * p = &a;
//	*p = 10;
//	p = &b;
//}
////指针变量的 *右边加const
////可以改变指针变量指向的数据的值，不可以改变指针变量的值
//void text3(void)
//{
//	int a = 10;
//	int b = 10;
//	int* const p = &a;
//	*p = 10;
//	p = &b;
//}
////指针变量的 *左右两边都加const
////不可以改变指针变量指向的数据的值，也不可以改变指针变量的值
//void text4(void)
//{
//	int a = 10;
//	int b = 10;
//	int const * const p = &a;
//	*p = 10;
//	p = &b;
//}
//
//int main()
//{
//	text1();
//	text2();
//	text3();
//	text4();
//	return 0;
//
//}


/*
* 5. 指针运算
指针的基本运算有三种，分别是：
*/
/*
* 一、 指针+-整数//最常见于数组
*/

//int main()
//{
//	int a[5] = {0,1,2,3,4};
//
//	int sz = sizeof(a) / sizeof(a[0]);//数组长度
//
//	//正序输出数组
//	for (int i = 0; i < sz; i++)
//	{
//		printf("a[%d]=%d\n",i,*(a+i));
//	}
//	//逆序输出数组
//	for (int i = 0; i < sz; i++)
//	{
//		printf("a[%d]=%d\n", i, *(&a[sz-1] - i));
//	}
//
//	return 0;
//
//}



///*
//* 二、 指针-指针//可以用于字符数量统计
//*/
//
//int my_strlen(char* my_str)
//{
//	char* a = my_str;
//	while (*a!='\0')
//	{
//		a++;
//	}
//	return a - my_str;
//}
//
//int main()
//{
//	char my_str[50] = {0};
//	scanf("%s", my_str);
//	printf("\"%s\" 有%d个字符", my_str, my_strlen(my_str));
//	return 0;
//}

/*
* 三、 指针的关系运算
*/

//int main()
//{
//	int a[5] = {0,1,2,3,4};
//
//	int sz = sizeof(a) / sizeof(a[0]);//数组长度
//
//	int* p = a;
//	//正序输出数组
//	for (p = a; p < a+sz; p++)
//	{
//		printf("%d\n",*p);
//	}
//
//	return 0;
//
//}


/*
* 
* 野指针
*/

/*
*	1/指针未初始化
* 
*	例
*	int *p;
*	*p=20;
*/
/*
*	2/指针越界访问
*	int a[2]={0};
*	int *p=a;
*	*(p+10)=50;
*/
/*
*	3/指针指向的空间释放
*	int * text()
*	{
*		int n=0;
*		return &n;
	}
	//即返回局部变量的指针
*/


/*
*	如何规避
*	1.初始化
*	int a=10;
*	int *p=&a;
*	int *p1=NULL;
//暂时不知道放哪个变量的地址可以先赋值为NULL。避免出现意外情况

*	2.不要越界访问
	主要就是数组越界
	3.指针变量不再使⽤时，及时置NULL，指针使⽤之前检查有效性（可以使用断言）
	4.避免返回局部变量的地址
*/

//断言
	// assert() 宏接受⼀个表达式作为参数。如果该表达式为真（返回值⾮零）， assert() 不会产⽣
	//任何作⽤，程序继续运⾏。如果该表达式为假（返回值为零）， assert() 就会报错，在标准错误
	//流 stderr 中写⼊⼀条错误信息，显⽰没有通过的表达式，以及包含这个表达式的⽂件名和⾏号。
	// 
	// assert() 的使⽤对程序员是⾮常友好的，使⽤ assert() 有⼏个好处：它不仅能⾃动标识⽂件和
	//出问题的⾏号，还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问
	//题，不需要再做断⾔，就在 #include <assert.h> 语句的前⾯，定义⼀个宏 NDEBUG
//#define NDEBUG
#include <assert.h>
int main()
{
	int a[5] = {0,1,2,3,4};

	int sz = sizeof(a) / sizeof(a[0]);//数组长度

	int* p = a;

	assert(p==NULL);
	//可以用来检测数据是否合规，

	//正序输出数组
	for (p = a; p < a+sz; p++)
	{
		printf("%d\n",*p);
	}

	return 0;

}